
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>【Prometheus】PromQL 万字详解</title>
            </head>
            <body>
            <a href="https://andyoung.blog.csdn.net">原作者博客</a>
            <div id="content_views" class="markdown_views prism-atom-one-light">
                    <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                        <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                    </svg>
                    <p>在上一文当中，通过Node Exporter暴露的HTTP服务，Prometheus可以采集到当前主机所有监控指标的样本数据。例如：</p> 
<pre><code># HELP node_cpu Seconds the cpus spent in each mode.
# TYPE node_cpu counter
node_cpu{cpu="cpu0",mode="idle"} 362812.7890625
# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 3.0703125
</code></pre> 
<p>其中非#开头的每一行表示当前Node Exporter采集到的一个监控样本：node_cpu和node_load1表明了当前指标的名称、大括号中的标签则反映了当前样本的一些特征和维度、浮点数则是该监控样本的具体值。</p> 
<h2><a id="_16"></a>样本</h2> 
<p>Prometheus会将所有采集到的样本数据以时间序列（time-series）的方式保存在内存数据库中，并且定时保存到硬盘上。time-series是按照时间戳和值的序列顺序存放的，我们称之为向量(vector). 每条time-series通过指标名称(metrics name)和一组标签集(labelset)命名。如下所示，可以将time-series理解为一个以时间为Y轴的数字矩阵：</p> 
<pre><code>  ^
  │   . . . . . . . . . . . . . . . . .   . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . .   . . . . . . . .   node_load1{}
  │     . . . . . . . . . . . . . . . .   . .  
  v
    &lt;------------------ 时间 ----------------&gt;
</code></pre> 
<p>在time-series中的每一个点称为一个样本（sample），样本由以下三部分组成：</p> 
<ul><li>指标(metric)：metric name和描述当前样本特征的labelsets;</li><li>时间戳(timestamp)：一个精确到毫秒的时间戳;</li><li>样本值(value)： 一个float64的浮点型数据表示当前样本的值。</li></ul> 
<pre><code>&lt;--------------- metric ---------------------&gt;&lt;-timestamp -&gt;&lt;-value-&gt;
http_request_total{status="200", method="GET"}@1434417560938 =&gt; 94355
http_request_total{status="200", method="GET"}@1434417561287 =&gt; 94334

http_request_total{status="404", method="GET"}@1434417560938 =&gt; 38473
http_request_total{status="404", method="GET"}@1434417561287 =&gt; 38544

http_request_total{status="200", method="POST"}@1434417560938 =&gt; 4748
http_request_total{status="200", method="POST"}@1434417561287 =&gt; 4785
</code></pre> 
<h2><a id="Metric_52"></a>指标(Metric)</h2> 
<p>在形式上，所有的指标(Metric)都通过如下格式标示：</p> 
<pre><code>&lt;metric name&gt;{&lt;label name&gt;=&lt;label value&gt;, ...}
</code></pre> 
<p>指标的名称(metric name)可以反映被监控样本的含义（比如，<code>http_request_total</code> - 表示当前系统接收到的HTTP请求总量）。指标名称只能由ASCII字符、数字、下划线以及冒号组成并必须符合正则表达式<code>[a-zA-Z_:][a-zA-Z0-9_:]*</code>。</p> 
<p>标签(label)反映了当前样本的特征维度，通过这些维度Prometheus可以对样本数据进行<strong>过滤</strong>，聚合等。标签的名称只能由ASCII字符、数字以及下划线组成并满足正则表达式<code>[a-zA-Z_][a-zA-Z0-9_]*</code>。（可以根据标签label 来筛选数据）</p> 
<p>其中以<code>__</code>作为前缀的标签，是系统保留的关键字，只能在系统内部使用。标签的值则可以包含任何Unicode编码的字符。在Prometheus的底层实现中指标名称实际上是以<code>__name__=&lt;metric name&gt;</code>的形式保存在数据库中的，因此以下两种方式均表示的同一条time-series：</p> 
<p><code>api_http_requests_total{method="POST", handler="/messages"}</code></p> 
<p>等同于：</p> 
<pre><code>{__name__="api_http_requests_total"，method="POST", handler="/messages"}
</code></pre> 
<p>在Prometheus源码中也可以找到指标(Metric)对应的数据结构，如下所示：</p> 
<pre><code>type Metric LabelSet

type LabelSet map[LabelName]LabelValue

type LabelName string

type LabelValue string
</code></pre> 
<p>再来举个例子，一条 Prometheus 数据由一个指标名称（metric）和 N 个标签（label，N &gt;= 0）组成的，比如下面这个例子：</p> 
<pre><code>promhttp_metric_handler_requests_total{code="200",instance="192.168.0.107:9090",job="prometheus"} 106
</code></pre> 
<p>这条数据的指标名称为 <code>promhttp_metric_handler_requests_total</code>，并且包含三个标签 <code>code</code>、<code>instance</code> 和 <code>job</code>，这条记录的值为 106。上面说过，Prometheus 是一个时序数据库，相同指标相同标签的数据构成一条时间序列。如果以传统数据库的概念来理解时序数据库，可以把指标名当作表名，标签是字段，timestamp 是主键，还有一个 float64 类型的字段表示值（Prometheus 里面所有值都是按 float64 存储）。</p> 
<p>这种数据模型和 OpenTSDB 的数据模型是比较类似的，详细的信息可以参考官网文档 Data model。另外，关于指标和标签的命名，官网有一些指导性的建议，可以参考 Metric and label naming 。</p> 
<h2><a id="Metrics_100"></a>Metrics类型</h2> 
<p>在上一小节中我们带领读者了解了Prometheus的底层数据模型，在Prometheus的存储实现上所有的监控样本都是以time-series的形式保存在Prometheus内存的TSDB（时序数据库）中，而time-series所对应的监控指标(metric)也是通过labelset进行唯一命名的。</p> 
<p>从存储上来讲所有的监控指标metric都是相同的，但是在不同的场景下这些metric又有一些细微的差异。 例如，在Node Exporter返回的样本中指标node_load1反应的是当前系统的负载状态，随着时间的变化这个指标返回的样本数据是在不断变化的。而指标node_cpu所获取到的样本数据却不同，它是一个持续增大的值，因为其反应的是CPU的累积使用时间，从理论上讲只要系统不关机，这个值是会无限变大的。</p> 
<p>虽然 Prometheus 里存储的数据都是 float64 的一个数值，为了能够帮助用户理解和区分这些不同监控指标之间的差异，Prometheus按类型来分定义了4种不同的指标类型(metric type)，可以把 Prometheus 的数据分成四大类：</p> 
<ul><li>Counter</li><li>Gauge</li><li>Histogram</li><li>Summary</li></ul> 
<p>在Exporter返回的样本数据中，其注释中也包含了该样本的类型。例如：</p> 
<pre><code># HELP node_cpu Seconds the cpus spent in each mode.
# TYPE node_cpu counter
node_cpu{cpu="cpu0",mode="idle"} 362812.7890625
</code></pre> 
<p>Counter 用于计数，例如：请求次数、任务完成数、错误发生次数，这个值会一直增加，不会减少。Gauge 就是一般的数值，可大可小，例如：温度变化、内存使用变化。Histogram 是直方图，或称为柱状图，常用于跟踪事件发生的规模，例如：请求耗时、响应大小。</p> 
<p>它特别之处是可以对记录的内容进行分组，提供 count 和 sum 的功能。Summary 和 Histogram 十分相似，也用于跟踪事件发生的规模，不同之处是，它提供了一个 quantiles 的功能，可以按百分比划分跟踪的结果。例如：quantile 取值 0.95，表示取采样值里面的 95% 数据。更多信息可以参考官网文档 Metric types，Summary 和 Histogram 的概念比较容易混淆，属于比较高阶的指标类型，可以参考 Histograms and summaries 这里的说明。</p> 
<p>这四种类型的数据只在指标的提供方作区分，也就是上面说的 Exporter，如果你需要编写自己的 Exporter 或者在现有系统中暴露供 Prometheus 抓取的指标，你可以使用 Prometheus client libraries，这个时候你就需要考虑不同指标的数据类型了。如果你不用自己实现，而是直接使用一些现成的 Exporter，然后在 Prometheus 里查查相关的指标数据，那么可以不用太关注这块，不过理解 Prometheus 的数据类型，对写出正确合理的 PromQL 也是有帮助的。</p> 
<h2><a id="_PromQL_135"></a><strong>学习 PromQL</strong></h2> 
<p>通过上面的步骤安装好 Prometheus 之后，我们现在可以开始体验 Prometheus 了。Prometheus 提供了可视化的 Web UI 方便我们操作，直接访问 <code>http://localhost:9090/</code> 即可，它默认会跳转到 Graph 页面：</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/054f16ae0d398ec5c50eeb9a649942e9.png" alt="图片"></p> 
<p>第一次访问这个页面可能会不知所措，我们可以先看看其他菜单下的内容，比如：Alerts 展示了定义的所有告警规则，Status 可以查看各种 Prometheus 的状态信息，有 Runtime &amp; Build Information、Command-Line Flags、Configuration、Rules、Targets、Service Discovery 等等。</p> 
<p>实际上 Graph 页面才是 Prometheus 最强大的功能，在这里我们可以使用 Prometheus 提供的一种特殊表达式来查询监控数据，这个表达式被称为 PromQL（Prometheus Query Language）。通过 PromQL 不仅可以在 Graph 页面查询数据，而且还可以通过 Prometheus 提供的 HTTP API 来查询。查询的监控数据有列表和曲线图两种展现形式（对应上图中 Console 和 Graph 这两个标签）。</p> 
<p>我们上面说过，Prometheus 自身也暴露了很多的监控指标，也可以在 Graph 页面查询，展开 Execute 按钮旁边的下拉框，可以看到很多指标名称，我们随便选一个，譬如：<code>promhttp_metric_handler_requests_total</code>，这个指标表示 <code>/metrics</code> 页面的访问次数，Prometheus 就是通过这个页面来抓取自身的监控数据的。在 Console 标签中查询结果如下：</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/7d4c57b23f2687f5544d140570c238f9.png" alt="图片"></p> 
<p>上面在介绍 Prometheus 的配置文件时，可以看到 <code>scrape_interval</code> 参数是 15s，也就是说 Prometheus 每 15s 访问一次 <code>/metrics</code> 页面，所以我们过 15s 刷新下页面，可以看到指标值会自增。在 Graph 标签中可以看得更明显：</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/61770c45b810cc9061412f2f9d85bec9.png" alt="图片"></p> 
<p>我们从一些例子开始学习 PromQL，最简单的 PromQL 就是直接输入指标名称，比如：</p> 
<pre><code># 表示 Prometheus 能否抓取 target 的指标，用于 target 的健康检查  
up  
</code></pre> 
<p>这条语句会查出 Prometheus 抓取的所有 target 当前运行情况，譬如下面这样：</p> 
<pre><code>up{instance="192.168.0.107:9090",job="prometheus"}    1  
up{instance="192.168.0.108:9090",job="prometheus"}    1  
up{instance="192.168.0.107:9100",job="server"}    1  
up{instance="192.168.0.108:9104",job="mysql"}    0  
</code></pre> 
<p>也可以指定某个 label 来查询：</p> 
<pre><code>up{job="prometheus"}  
</code></pre> 
<p>这种写法被称为 Instant vector selectors，这里不仅可以使用 <code>=</code> 号，还可以使用 <code>!=</code>、<code>=~</code>、<code>!~</code>，比如下面这样：</p> 
<pre><code>up{job!="prometheus"}  
up{job=~"server|mysql"}  
up{job=~"192\.168\.0\.107.+"}  
</code></pre> 
<p><code>=~</code> 是根据正则表达式来匹配，必须符合 RE2 的语法。</p> 
<p>和 Instant vector selectors 相应的，还有一种选择器，叫做 Range vector selectors，它可以查出一段时间内的所有数据：</p> 
<pre><code>http_requests_total[5m]  
</code></pre> 
<p>这条语句查出 5 分钟内所有抓取的 HTTP 请求数，注意它返回的<strong>数据类型是</strong> <code>Range vector</code>（<strong>区间向量</strong>），没办法在 Graph 上显示成曲线图，一般情况下，会用在 Counter 类型的指标上，并和 <code>rate()</code> 或 <code>irate()</code> 函数一起使用（注意 rate 和 irate 的区别）。</p> 
<pre><code># 计算的是每秒的平均值，适用于变化很慢的 counter  
# per-second average rate of increase, for slow-moving counters  
rate(http_requests_total[5m])  
   
# 计算的是每秒瞬时增加速率，适用于变化很快的 counter  
# per-second instant rate of increase, for volatile and fast-moving counters  
irate(http_requests_total[5m])  
</code></pre> 
<p>此外，PromQL 还支持 <code>count</code>、<code>sum</code>、<code>min</code>、<code>max</code>、<code>topk</code> 等 聚合操作，还支持 <code>rate</code>、<code>abs</code>、<code>ceil</code>、<code>floor</code> 等一堆的 内置函数，更多的例子，还是上官网学习吧。如果感兴趣，我们还可以把 PromQL 和 SQL 做一个对比，会发现 PromQL 语法更简洁，查询性能也更高。</p> 
<p>除了使用m表示分钟以外，PromQL的时间范围选择器支持其它时间单位：</p> 
<ul><li>s - 秒</li><li>m - 分钟</li><li>h - 小时</li><li>d - 天</li><li>w - 周</li><li>y - 年</li></ul> 
<h2><a id="_214"></a>时间位移操作</h2> 
<p>在<mark>瞬时向量表达式</mark>或者<mark>区间向量表达式</mark>中，都是以当前时间为基准：</p> 
<pre><code>http_request_total{} # 瞬时向量表达式，选择当前最新的数据
http_request_total{}[5m] # 区间向量表达式，选择以当前时间为基准，5分钟内的数据
</code></pre> 
<p>而如果我们想查询，5分钟前的瞬时样本数据，或昨天一天的区间内的样本数据呢? 这个时候我们就可以使用位移操作，位移操作的关键字为<strong>offset</strong>。</p> 
<p>可以使用offset时间位移操作：</p> 
<pre><code>http_request_total{} offset 5m
http_request_total{}[1d] offset 1d
</code></pre> 
<h2><a id="_232"></a>使用聚合操作</h2> 
<p>一般来说，如果描述样本特征的标签(label)在并非唯一的情况下，通过PromQL查询数据，会返回多条满足这些特征维度的时间序列。而PromQL提供的聚合操作可以用来对这些时间序列进行处理，形成一条新的时间序列：</p> 
<pre><code># 查询系统所有http请求的总量
sum(http_request_total)

# 按照mode计算主机CPU的平均使用时间
avg(node_cpu) by (mode)

# 按照主机查询各个主机的CPU使用率
sum(sum(irate(node_cpu{mode!='idle'}[5m]))  / sum(irate(node_cpu[5m]))) by (instance)
</code></pre> 
<blockquote> 
 <p>by 分组统计</p> 
</blockquote> 
<ul><li>一般的查询</li></ul> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/ffd1fbf2652be544bd85f64e8d4722c4.png" alt="image-20221118133722271"></p> 
<ul><li>聚合查询（可以理解成mysql的聚合查询，分组统计等）</li></ul> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/45f15d12cc3bce9dc2afd7e0b4e00dc8.png" alt="image-20221118133822129"></p> 
<h2><a id="_255"></a>标量和字符串</h2> 
<p>除了使用瞬时向量表达式和区间向量表达式以外，PromQL还直接支持用户使用标量(Scalar)和字符串(String)。</p> 
<h3><a id="Scalar_259"></a>标量（Scalar）：一个浮点型的数字值</h3> 
<p>标量只有一个数字，没有时序。</p> 
<p>例如：</p> 
<p><code>10</code></p> 
<blockquote> 
 <p>需要注意的是，当使用表达式count(http_requests_total)，返回的数据类型，依然是瞬时向量。用户可以通过内置函数scalar()将单个瞬时向量转换为标量。</p> 
</blockquote> 
<h3><a id="String_269"></a>字符串（String）：一个简单的字符串值</h3> 
<p>直接使用字符串，作为PromQL表达式，则会直接返回字符串。</p> 
<pre><code>"this is a string"
'these are unescaped: \n \\ \t'
`these are not unescaped: \n ' " \t`
</code></pre> 
<h2><a id="PromQL_279"></a>合法的PromQL表达式</h2> 
<p>所有的PromQL表达式都必须至少包含一个指标名称(例如http_request_total)，或者一个不会匹配到空字符串的标签过滤器(例如{code=“200”})。</p> 
<p>因此以下两种方式，均为合法的表达式：</p> 
<pre><code>http_request_total # 合法
http_request_total{} # 合法
{method="get"} # 合法
</code></pre> 
<p>而如下表达式，则不合法：</p> 
<pre><code>{job=~".*"} # 不合法
</code></pre> 
<p>同时，除了使用<code>&lt;metric name&gt;{label=value}</code>的形式以外，我们还可以使用内置的<code>__name__</code>标签来指定监控指标名称</p> 
<pre><code>{__name__=~"http_request_total"} # 合法
{__name__=~"node_disk_bytes_read|node_disk_bytes_written"} # 合法
</code></pre> 
<h2><a id="HTTP_API_306"></a>HTTP API</h2> 
<p>我们不仅仅可以在 Prometheus 的 Graph 页面查询 PromQL，Prometheus 还提供了一种 HTTP API 的方式，可以更灵活的将 PromQL 整合到其他系统中使用，譬如下面要介绍的 Grafana，就是通过 Prometheus 的 HTTP API 来查询指标数据的。实际上，我们在 Prometheus 的 Graph 页面查询也是使用了 HTTP API。</p> 
<p>我们看下 Prometheus 的 HTTP API 官方文档，它提供了下面这些接口：</p> 
<ul><li>GET /api/v1/query</li><li>GET /api/v1/query_range</li><li>GET /api/v1/series</li><li>GET /api/v1/label/&lt;label_name&gt;/values</li><li>GET /api/v1/targets</li><li>GET /api/v1/rules</li><li>GET /api/v1/alerts</li><li>GET /api/v1/targets/metadata</li><li>GET /api/v1/alertmanagers</li><li>GET /api/v1/status/config</li><li>GET /api/v1/status/flags</li></ul> 
<p>从 Prometheus v2.1 开始，又新增了几个用于管理 TSDB 的接口：</p> 
<ul><li>POST /api/v1/admin/tsdb/snapshot</li><li>POST /api/v1/admin/tsdb/delete_series</li><li>POST /api/v1/admin/tsdb/clean_tombstones</li></ul> 
<p>使用PromQL除了能够方便的按照查询和过滤时间序列以外，PromQL还支持丰富的操作符，用户可以使用这些操作符对进一步的对事件序列进行二次加工。这些操作符包括：数学运算符，逻辑运算符，布尔运算符等等。</p> 
<h2><a id="_332"></a>数学运算</h2> 
<p>例如，我们可以通过指标<code>node_memory_MemAvailable_bytes</code>获取当前主机可用的内存空间大小，其样本单位为Bytes。这是如果客户端要求使用MB作为单位响应数据，那只需要将查询到的时间序列的样本值进行单位换算即可：</p> 
<pre><code>node_memory_MemAvailable_bytes/ (1024 * 1024)
</code></pre> 
<p><code>node_memory_MemAvailable_bytes</code>表达式会查询出所有满足表达式条件的时间序列，在上一小节中我们称该表达式为瞬时向量表达式，而返回的结果成为瞬时向量。</p> 
<p>当瞬时向量与标量之间进行数学运算时，数学运算符会依次作用域瞬时向量中的每一个样本值，从而得到一组新的时间序列。</p> 
<p>而如果是瞬时向量与瞬时向量之间进行数学运算时，过程会相对复杂一点。 例如，如果我们想根据node_disk_bytes_written和node_disk_bytes_read获取主机磁盘IO的总量，可以使用如下表达式：</p> 
<pre><code>node_disk_bytes_written + node_disk_bytes_read
</code></pre> 
<p>那这个表达式是如何工作的呢？依次找到与左边向量元素匹配（标签完全一致）的右边向量元素进行运算，如果没找到匹配元素，则直接丢弃。同时新的时间序列将不会包含指标名称。 该表达式返回结果的示例如下所示：</p> 
<pre><code>{device="sda",instance="localhost:9100",job="node_exporter"}=&gt;1634967552@1518146427.807 + 864551424@1518146427.807
{device="sdb",instance="localhost:9100",job="node_exporter"}=&gt;0@1518146427.807 + 1744384@1518146427.807
</code></pre> 
<p>PromQL支持的所有数学运算符如下所示：</p> 
<ul><li><code>+</code> (加法)</li><li><code>-</code> (减法)</li><li><code>*</code> (乘法)</li><li><code>/</code> (除法)</li><li><code>%</code> (求余)</li><li><code>^</code> (幂运算)</li></ul> 
<h2><a id="_368"></a>使用布尔运算过滤时间序列</h2> 
<p>在PromQL通过标签匹配模式，用户可以根据时间序列的特征维度对其进行查询。而布尔运算则支持用户根据时间序列中样本的值，对时间序列进行过滤。</p> 
<p>例如，通过数学运算符我们可以很方便的计算出，当前所有主机节点的内存使用率：</p> 
<pre><code>(  node_memory_MemTotal_bytes - node_memory_MemFree_bytes) / node_memory_MemTotal_bytes
</code></pre> 
<p>而系统管理员在排查问题的时候可能只想知道当前内存使用率超过95%的主机呢？通过使用布尔运算符可以方便的获取到该结果：</p> 
<pre><code>(  node_memory_MemTotal_bytes - node_memory_MemFree_bytes) / node_memory_MemTotal_bytes &gt; 0.95
</code></pre> 
<p>瞬时向量与标量进行布尔运算时，PromQL依次比较向量中的所有时间序列样本的值，如果比较结果为true则保留，反之丢弃。</p> 
<p>瞬时向量与瞬时向量直接进行布尔运算时，同样遵循默认的匹配模式：依次找到与左边向量元素匹配（标签完全一致）的右边向量元素进行相应的操作，如果没找到匹配元素，则直接丢弃。</p> 
<p>目前，Prometheus支持以下布尔运算符如下：</p> 
<ul><li><code>==</code> (相等)</li><li><code>!=</code> (不相等)</li><li><code>&gt;</code> (大于)</li><li><code>&lt;</code> (小于)</li><li><code>&gt;=</code> (大于等于)</li><li><code>&lt;=</code> (小于等于)</li></ul> 
<h2><a id="bool_401"></a>使用bool修饰符改变布尔运算符的行为</h2> 
<p>布尔运算符的默认行为是对时序数据进行过滤。而在其它的情况下我们可能需要的是真正的布尔结果。例如，只需要知道当前模块的HTTP请求量是否&gt;=1000，如果大于等于1000则返回1（true）否则返回0（false）。这时可以使用bool修饰符改变布尔运算的默认行为。 例如：</p> 
<pre><code>http_requests_total &gt; bool 1000
</code></pre> 
<p>使用bool修改符后，布尔运算不会对时间序列进行过滤，而是直接依次瞬时向量中的各个样本数据与标量的比较结果0或者1。从而形成一条新的时间序列。</p> 
<pre><code>http_requests_total{code="200",handler="query",instance="localhost:9090",job="prometheus",method="get"}  1
http_requests_total{code="200",handler="query_range",instance="localhost:9090",job="prometheus",method="get"}  0
</code></pre> 
<p>同时需要注意的是，如果是在两个标量之间使用布尔运算，则必须使用bool修饰符</p> 
<pre><code>2 == bool 2 # 结果为1
</code></pre> 
<h2><a id="_424"></a>使用集合运算符</h2> 
<p>使用瞬时向量表达式能够获取到一个包含多个时间序列的集合，我们称为瞬时向量。 通过集合运算，可以在两个瞬时向量与瞬时向量之间进行相应的集合操作。目前，Prometheus支持以下集合运算符：</p> 
<ul><li><code>and</code> (并且)</li><li><code>or</code> (或者)</li><li><code>unless</code> (排除)</li></ul> 
<p><em><strong>vector1 and vector2</strong></em> 会产生一个由vector1的元素组成的新的向量。该向量包含vector1中完全匹配vector2中的元素组成。</p> 
<p><em><strong>vector1 or vector2</strong></em> 会产生一个新的向量，该向量包含vector1中所有的样本数据，以及vector2中没有与vector1匹配到的样本数据。</p> 
<p><em><strong>vector1 unless vector2</strong></em> 会产生一个新的向量，新向量中的元素由vector1中没有与vector2匹配的元素组成。</p> 
<h2><a id="_438"></a>操作符优先级</h2> 
<p>对于复杂类型的表达式，需要了解运算操作的运行优先级</p> 
<p>例如，查询主机的CPU使用率，可以使用表达式：</p> 
<pre><code>100 * (1 - avg (irate(node_cpu{mode='idle'}[5m])) by(job) )
</code></pre> 
<p>其中irate是PromQL中的内置函数，用于计算区间向量中时间序列每秒的即时增长率。关于内置函数的部分，会在下一节详细介绍。</p> 
<p>在PromQL操作符中优先级由高到低依次为：</p> 
<ol><li><code>^</code></li><li><code>*, /, %</code></li><li><code>+, -</code></li><li><code>==, !=, &lt;=, &lt;, &gt;=, &gt;</code></li><li><code>and, unless</code></li><li><code>or</code></li></ol> 
<h2><a id="_459"></a>匹配模式详解</h2> 
<p>向量与向量之间进行运算操作时会基于默认的匹配规则：依次找到与左边向量元素匹配（标签完全一致）的右边向量元素进行运算，如果没找到匹配元素，则直接丢弃。</p> 
<p>接下来将介绍在PromQL中有两种典型的匹配模式：一对一（one-to-one）,多对一（many-to-one）或一对多（one-to-many）。</p> 
<h3><a id="_465"></a>一对一匹配</h3> 
<p>一对一匹配模式会从操作符两边表达式获取的瞬时向量依次比较并找到唯一匹配(标签完全一致)的样本值。默认情况下，使用表达式：</p> 
<pre><code>vector1 &lt;operator&gt; vector2
</code></pre> 
<p>在操作符两边表达式标签不一致的情况下，可以使用on(label list)或者ignoring(label list）来修改便签的匹配行为。使用ignoreing可以在匹配时忽略某些便签。而on则用于将匹配行为限定在某些便签之内。</p> 
<pre><code>&lt;vector expr&gt; &lt;bin-op&gt; ignoring(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; on(&lt;label list&gt;) &lt;vector expr&gt;
</code></pre> 
<p>例如当存在样本：</p> 
<pre><code>method_code:http_errors:rate5m{method="get", code="500"}  24
method_code:http_errors:rate5m{method="get", code="404"}  30
method_code:http_errors:rate5m{method="put", code="501"}  3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21

method:http_requests:rate5m{method="get"}  600
method:http_requests:rate5m{method="del"}  34
method:http_requests:rate5m{method="post"} 120
</code></pre> 
<p>使用PromQL表达式：</p> 
<pre><code>method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m
</code></pre> 
<p>该表达式会返回在过去5分钟内，HTTP请求状态码为500的在所有请求中的比例。如果没有使用ignoring(code)，操作符两边表达式返回的瞬时向量中将找不到任何一个标签完全相同的匹配项。</p> 
<p>因此结果如下：</p> 
<pre><code>{method="get"}  0.04            //  24 / 600
{method="post"} 0.05            //   6 / 120
</code></pre> 
<p>同时由于method为put和del的样本找不到匹配项，因此不会出现在结果当中。</p> 
<h3><a id="_513"></a>多对一和一对多</h3> 
<p>多对一和一对多两种匹配模式指的是“一”侧的每一个向量元素可以与"多"侧的多个元素匹配的情况。在这种情况下，必须使用group修饰符：group_left或者group_right来确定哪一个向量具有更高的基数（充当“多”的角色）。</p> 
<pre><code>&lt;vector expr&gt; &lt;bin-op&gt; ignoring(&lt;label list&gt;) group_left(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; ignoring(&lt;label list&gt;) group_right(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; on(&lt;label list&gt;) group_left(&lt;label list&gt;) &lt;vector expr&gt;
&lt;vector expr&gt; &lt;bin-op&gt; on(&lt;label list&gt;) group_right(&lt;label list&gt;) &lt;vector expr&gt;
</code></pre> 
<p>多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况。因此需要使用ignoring和on修饰符来排除或者限定匹配的标签列表。</p> 
<p>例如,使用表达式：</p> 
<pre><code>method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m
</code></pre> 
<p>该表达式中，左向量<code>method_code:http_errors:rate5m</code>包含两个标签method和code。而右向量<code>method:http_requests:rate5m</code>中只包含一个标签method，因此匹配时需要使用ignoring限定匹配的标签为code。 在限定匹配标签后，右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一，需要使用group修饰符group_left指定左向量具有更好的基数。</p> 
<p>最终的运算结果如下：</p> 
<pre><code>{method="get", code="500"}  0.04            //  24 / 600
{method="get", code="404"}  0.05            //  30 / 600
{method="post", code="500"} 0.05            //   6 / 120
{method="post", code="404"} 0.175           //  21 / 120
</code></pre> 
<blockquote> 
 <p>提醒：group修饰符只能在比较和数学运算符中使用。在逻辑运算and,unless和or才注意操作中默认与右向量中的所有元素进行匹配。</p> 
</blockquote> 
<h2><a id="PromQL_547"></a>PromQL聚合操作</h2> 
<p>Prometheus还提供了下列内置的聚合操作符，这些操作符作用域瞬时向量。可以将瞬时表达式返回的样本数据进行聚合，形成一个新的时间序列。</p> 
<ul><li><code>sum</code> (求和)</li><li><code>min</code> (最小值)</li><li><code>max</code> (最大值)</li><li><code>avg</code> (平均值)</li><li><code>stddev</code> (标准差)</li><li><code>stdvar</code> (标准方差)</li><li><code>count</code> (计数)</li><li><code>count_values</code> (对value进行计数)</li><li><code>bottomk</code> (后n条时序)</li><li><code>topk</code> (前n条时序)</li><li><code>quantile</code> (分位数)</li></ul> 
<p>使用聚合操作的语法如下：</p> 
<pre><code>&lt;aggr-op&gt;([parameter,] &lt;vector expression&gt;) [without|by (&lt;label list&gt;)]
</code></pre> 
<p>其中只有<code>count_values</code>, <code>quantile</code>, <code>topk</code>, <code>bottomk</code>支持参数(parameter)。</p> 
<p>without用于从计算结果中移除列举的标签，而保留其它标签。by则正好相反，结果向量中只保留列出的标签，其余标签则移除。通过without和by可以按照样本的问题对数据进行聚合。</p> 
<p>例如：</p> 
<pre><code>sum(http_requests_total) without (instance)
</code></pre> 
<p>等价于</p> 
<pre><code>sum(http_requests_total) by (code,handler,job,method)
</code></pre> 
<p>如果只需要计算整个应用的HTTP请求总量，可以直接使用表达式：</p> 
<pre><code>sum(http_requests_total)
</code></pre> 
<p>count_values用于时间序列中每一个样本值出现的次数。count_values会为每一个唯一的样本值输出一个时间序列，并且每一个时间序列包含一个额外的标签。</p> 
<pre><code>count_values("count", http_requests_total)
</code></pre> 
<p>topk和bottomk则用于对样本值进行排序，返回当前样本值前n位，或者后n位的时间序列。</p> 
<p>获取HTTP请求数前5位的时序样本数据，可以使用表达式：</p> 
<pre><code>topk(5, http_requests_total)
</code></pre> 
<p>quantile用于计算当前样本数据值的分布情况quantile(φ, express)其中0 ≤ φ ≤ 1。</p> 
<p>例如，当φ为0.5时，即表示找到当前样本数据中的中位数：</p> 
<pre><code>quantile(0.5, http_requests_total)
</code></pre> 
<h2><a id="PromQL_613"></a>PromQL内置函数</h2> 
<p>在上一小节中，我们已经看到了类似于irate()这样的函数，可以帮助我们计算监控指标的增长率。除了irate以外，Prometheus还提供了其它大量的内置函数，可以对时序数据进行丰富的处理。本小节将带来读者了解一些常用的内置函数以及相关的使用场景和用法。</p> 
<h3><a id="Counter_617"></a>计算Counter指标增长率</h3> 
<p>我们知道Counter类型的监控指标其特点是只增不减，在没有发生重置（如服务器重启，应用重启）的情况下其样本值应该是不断增大的。为了能够更直观的表示样本数据的变化剧烈情况，需要计算样本的增长速率。</p> 
<p>如下图所示，样本增长率反映出了样本变化的剧烈程度：</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/194247b51b8521ea414a2707ae7c252f.png" alt="通过增长率表示样本的变化情况"></p> 
<p>increase(v range-vector)函数是PromQL中提供的众多内置函数之一。其中参数v是一个区间向量，increase函数获取区间向量中的第一个后最后一个样本并返回其增长量。因此，可以通过以下表达式Counter类型指标的增长率：</p> 
<pre><code>increase(node_cpu[2m]) / 120
</code></pre> 
<p>这里通过node_cpu[2m]获取时间序列最近两分钟的所有样本，increase计算出最近两分钟的增长量，最后除以时间120秒得到node_cpu样本在最近两分钟的平均增长率。并且这个值也近似于主机节点最近两分钟内的平均CPU使用率。</p> 
<p>除了使用increase函数以外，PromQL中还直接内置了rate(v range-vector)函数，rate函数可以直接计算区间向量v在时间窗口内平均增长速率。因此，通过以下表达式可以得到与increase函数相同的结果：</p> 
<pre><code>rate(node_cpu[2m])
</code></pre> 
<p>需要注意的是使用rate或者increase函数去计算样本的平均增长速率，容易陷入“长尾问题”当中，其无法反应在时间窗口内样本数据的突发变化。 例如，对于主机而言在2分钟的时间窗口内，可能在某一个由于访问量或者其它问题导致CPU占用100%的情况，但是通过计算在时间窗口内的平均增长率却无法反应出该问题。</p> 
<p>为了解决该问题，PromQL提供了另外一个灵敏度更高的函数irate(v range-vector)。irate同样用于计算区间向量的计算率，但是其反应出的是瞬时增长率。irate函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率。这种方式可以避免在时间窗口范围内的“长尾问题”，并且体现出更好的灵敏度，通过irate函数绘制的图标能够更好的反应样本数据的瞬时变化状态。</p> 
<pre><code>irate(node_cpu[2m])
</code></pre> 
<p>irate函数相比于rate函数提供了更高的灵敏度，不过当需要分析长期趋势或者在告警规则中，irate的这种灵敏度反而容易造成干扰。因此在长期趋势分析或者告警中更推荐使用rate函数。</p> 
<h3><a id="Gauge_651"></a>预测Gauge指标变化趋势</h3> 
<p>在一般情况下，系统管理员为了确保业务的持续可用运行，会针对服务器的资源设置相应的告警阈值。例如，当磁盘空间只剩512MB时向相关人员发送告警通知。 这种基于阈值的告警模式对于当资源用量是平滑增长的情况下是能够有效的工作的。 但是如果资源不是平滑变化的呢？ 比如有些某些业务增长，存储空间的增长速率提升了高几倍。这时，如果基于原有阈值去触发告警，当系统管理员接收到告警以后可能还没来得及去处理问题，系统就已经不可用了。 因此阈值通常来说不是固定的，需要定期进行调整才能保证该告警阈值能够发挥去作用。 那么还有没有更好的方法吗？</p> 
<p>PromQL中内置的predict_linear(v range-vector, t scalar) 函数可以帮助系统管理员更好的处理此类情况，predict_linear函数可以预测时间序列v在t秒后的值。它基于简单线性回归的方式，对时间窗口内的样本数据进行统计，从而可以对时间序列的变化趋势做出预测。例如，基于2小时的样本数据，来预测主机可用磁盘空间的是否在4个小时候被占满，可以使用如下表达式：</p> 
<pre><code>predict_linear(node_filesystem_free{job="node"}[2h], 4 * 3600) &lt; 0
</code></pre> 
<h3><a id="Histogram_661"></a>统计Histogram指标的分位数</h3> 
<p>在上方，我们介绍了Prometheus的四种监控指标类型，其中Histogram和Summary都可以用于统计和分析数据的分布情况。区别在于Summary是直接在客户端计算了数据分布的分位数情况。而Histogram的分位数计算需要通过histogram_quantile(φ float, b instant-vector)函数进行计算。其中φ（0&lt;φ&lt;1）表示需要计算的分位数，如果需要计算中位数φ取值为0.5，以此类推即可。</p> 
<p>以指标http_request_duration_seconds_bucket为例：</p> 
<pre><code># HELP http_request_duration_seconds request duration histogram
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.5"} 0
http_request_duration_seconds_bucket{le="1"} 1
http_request_duration_seconds_bucket{le="2"} 2
http_request_duration_seconds_bucket{le="3"} 3
http_request_duration_seconds_bucket{le="5"} 3
http_request_duration_seconds_bucket{le="+Inf"} 3
http_request_duration_seconds_sum 6
http_request_duration_seconds_count 3
</code></pre> 
<p>当计算9分位数时，使用如下表达式：</p> 
<pre><code>histogram_quantile(0.5, http_request_duration_seconds_bucket)
</code></pre> 
<p>通过对Histogram类型的监控指标，用户可以轻松获取样本数据的分布情况。同时分位数的计算，也可以非常方便的用于评判当前监控指标的服务水平。</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/973e54a53c93157543d130e2200aa703.png" alt="获取分布直方图的中位数"></p> 
<p>需要注意的是通过histogram_quantile计算的分位数，并非为精确值，而是通过http_request_duration_seconds_bucket和http_request_duration_seconds_sum近似计算的结果。</p> 
<h3><a id="_696"></a>动态标签替换</h3> 
<p>一般来说来说，使用PromQL查询到时间序列后，可视化工具会根据时间序列的标签来渲染图表。例如通过up指标可以获取到当前所有运行的Exporter实例以及其状态：</p> 
<pre><code>up{instance="localhost:8080",job="cadvisor"}    1
up{instance="localhost:9090",job="prometheus"}    1
up{instance="localhost:9100",job="node"}    1
</code></pre> 
<p>这是可视化工具渲染图标时可能根据，instance和job的值进行渲染，为了能够让客户端的图标更具有可读性，可以通过label_replace标签为时间序列添加额外的标签。label_replace的具体参数如下：</p> 
<pre><code>label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)
</code></pre> 
<p>该函数会依次对v中的每一条时间序列进行处理，通过regex匹配src_label的值，并将匹配部分relacement写入到dst_label标签中。如下所示：</p> 
<pre><code>label_replace(up, "host", "$1", "instance",  "(.*):.*")
</code></pre> 
<p>函数处理后，时间序列将包含一个host标签，host标签的值为Exporter实例的IP地址：</p> 
<pre><code>up{host="localhost",instance="localhost:8080",job="cadvisor"}    1
up{host="localhost",instance="localhost:9090",job="prometheus"}    1
up{host="localhost",instance="localhost:9100",job="node"} 1
</code></pre> 
<p>除了label_replace以外，Prometheus还提供了label_join函数，该函数可以将时间序列中v多个标签src_label的值，通过separator作为连接符写入到一个新的标签dst_label中:</p> 
<pre><code>label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...)
</code></pre> 
<p>label_replace和label_join函数提供了对时间序列标签的自定义能力，从而能够更好的于客户端或者可视化工具配合。</p> 
<h3><a id="_738"></a>其它内置函数</h3> 
<p>除了上文介绍的这些内置函数以外，PromQL还提供了大量的其它内置函数。这些内置函数包括一些常用的数学计算、日期等等。这里就不一一细讲，感兴趣的读者可以通过阅读Prometheus的官方文档，了解这些函数的使用方式。</p> 
<h2><a id="HTTP_APIPromQL_742"></a>在HTTP API中使用PromQL</h2> 
<p>Prometheus当前稳定的HTTP API可以通过/api/v1访问。</p> 
<h2><a id="API_746"></a>API响应格式</h2> 
<p>Prometheus API使用了JSON格式的响应内容。 当API调用成功后将会返回2xx的HTTP状态码。</p> 
<p>反之，当API调用失败时可能返回以下几种不同的HTTP状态码：</p> 
<ul><li>404 Bad Request：当参数错误或者缺失时。</li><li>422 Unprocessable Entity 当表达式无法执行时。</li><li>503 Service Unavailiable 当请求超时或者被中断时。</li></ul> 
<p>所有的API请求均使用以下的JSON格式：</p> 
<pre><code>{
  "status": "success" | "error",
  "data": &lt;data&gt;,

  // Only set if status is "error". The data field may still hold
  // additional data.
  "errorType": "&lt;string&gt;",
  "error": "&lt;string&gt;"
}
</code></pre> 
<h2><a id="HTTP_APIPromQL_772"></a>在HTTP API中使用PromQL</h2> 
<p>通过HTTP API我们可以分别通过/api/v1/query和/api/v1/query_range查询PromQL表达式当前或者一定时间范围内的计算结果。</p> 
<h3><a id="_776"></a>瞬时数据查询</h3> 
<p>通过使用QUERY API我们可以查询PromQL在特定时间点下的计算结果。</p> 
<pre><code>GET /api/v1/query
</code></pre> 
<p>URL请求参数：</p> 
<ul><li>query=：PromQL表达式。</li><li>time=：用于指定用于计算PromQL的时间戳。可选参数，默认情况下使用当前系统时间。</li><li>timeout=：超时设置。可选参数，默认情况下使用-query,timeout的全局设置。</li></ul> 
<p>例如使用以下表达式查询表达式up在时间点2015-07-01T20:10:51.781Z的计算结果：</p> 
<pre><code>$ curl 'http://localhost:9090/api/v1/query?query=up&amp;time=2015-07-01T20:10:51.781Z'
{
   "status" : "success",
   "data" : {
      "resultType" : "vector",
      "result" : [
         {
            "metric" : {
               "__name__" : "up",
               "job" : "prometheus",
               "instance" : "localhost:9090"
            },
            "value": [ 1435781451.781, "1" ]
         },
         {
            "metric" : {
               "__name__" : "up",
               "job" : "node",
               "instance" : "localhost:9100"
            },
            "value" : [ 1435781451.781, "0" ]
         }
      ]
   }
}
</code></pre> 
<h3><a id="_822"></a>响应数据类型</h3> 
<pre><code>{
  "resultType": "matrix" | "vector" | "scalar" | "string",
  "result": &lt;value&gt;
}
</code></pre> 
<p>PromQL表达式可能返回多种数据类型，在响应内容中使用resultType表示当前返回的数据类型，包括：</p> 
<ul><li>瞬时向量：vector</li></ul> 
<p>当返回数据类型resultType为vector时，result响应格式如下：</p> 
<pre><code>[
  {
    "metric": { "&lt;label_name&gt;": "&lt;label_value&gt;", ... },
    "value": [ &lt;unix_time&gt;, "&lt;sample_value&gt;" ]
  },
  ...
]
</code></pre> 
<p>其中metrics表示当前时间序列的特征维度，value只包含一个唯一的样本。</p> 
<ul><li>区间向量：matrix</li></ul> 
<p>当返回数据类型resultType为matrix时，result响应格式如下：</p> 
<pre><code>[
  {
    "metric": { "&lt;label_name&gt;": "&lt;label_value&gt;", ... },
    "values": [ [ &lt;unix_time&gt;, "&lt;sample_value&gt;" ], ... ]
  },
  ...
]
</code></pre> 
<p>其中metrics表示当前时间序列的特征维度，values包含当前事件序列的一组样本。</p> 
<ul><li>标量：scalar</li></ul> 
<p>当返回数据类型resultType为scalar时，result响应格式如下：</p> 
<pre><code>[ &lt;unix_time&gt;, "&lt;scalar_value&gt;" ]
</code></pre> 
<p>由于标量不存在时间序列一说，因此result表示为当前系统时间一个标量的值。</p> 
<ul><li>字符串：string</li></ul> 
<p>当返回数据类型resultType为string时，result响应格式如下：</p> 
<pre><code>[ &lt;unix_time&gt;, "&lt;string_value&gt;" ]
</code></pre> 
<p>字符串类型的响应内容格式和标量相同。</p> 
<h3><a id="_885"></a>区间数据查询</h3> 
<p>使用QUERY_RANGE API我们则可以直接查询PromQL表达式在一段时间返回内的计算结果。</p> 
<pre><code>GET /api/v1/query_range
</code></pre> 
<p>URL请求参数：</p> 
<ul><li>query=: PromQL表达式。</li><li>start=: 起始时间。</li><li>end=: 结束时间。</li><li>step=: 查询步长。</li><li>timeout=: 超时设置。可选参数，默认情况下使用-query,timeout的全局设置。</li></ul> 
<p>当使用QUERY_RANGE API查询PromQL表达式时，返回结果一定是一个区间向量：</p> 
<pre><code>{
  "resultType": "matrix",
  "result": &lt;value&gt;
}
</code></pre> 
<blockquote> 
 <p>需要注意的是，在QUERY_RANGE API中PromQL只能使用瞬时向量选择器类型的表达式。</p> 
</blockquote> 
<p>例如使用以下表达式查询表达式up在30秒范围内以15秒为间隔计算PromQL表达式的结果。</p> 
<pre><code>$ curl 'http://localhost:9090/api/v1/query_range?query=up&amp;start=2015-07-01T20:10:30.781Z&amp;end=2015-07-01T20:11:00.781Z&amp;step=15s'
{
   "status" : "success",
   "data" : {
      "resultType" : "matrix",
      "result" : [
         {
            "metric" : {
               "__name__" : "up",
               "job" : "prometheus",
               "instance" : "localhost:9090"
            },
            "values" : [
               [ 1435781430.781, "1" ],
               [ 1435781445.781, "1" ],
               [ 1435781460.781, "1" ]
            ]
         },
         {
            "metric" : {
               "__name__" : "up",
               "job" : "node",
               "instance" : "localhost:9091"
            },
            "values" : [
               [ 1435781430.781, "0" ],
               [ 1435781445.781, "0" ],
               [ 1435781460.781, "1" ]
            ]
         }
      ]
   }
}
</code></pre>
                </div>
            </body>
            </html>
            